入门Splay

您所在的位置:网站首页 alex merser 入门Splay

入门Splay

2023-08-31 06:40| 来源: 网络整理| 查看: 265

学着zkw突然回来整理平衡树

平衡树呢,是一种比较毒瘤的数据结构,

首先,其种类非常多,实现原理也各有差异,各有千秋,

而且其应用也非常广泛,

之前看见_rqy说不会线段树然后敲了一个平衡树A掉题目

所以我将分类整理各类平衡树

说到Splay,第一次正式学习是在qbxt的晚自习,然而并没有听懂,最近回来补发现并不难

因为平衡树主要操作无非分割和旋转两类整理操作,Splay是旋转操作,

所以Splay的主要操作核心就在于交换节点之间的关系,那么懂了思路以后,就全是模拟啦...

事实上学了Splay,旋转Treap是很简单的东西

平衡树-Splay

一种非常广泛的数据结构,主要的操作就是转...

Splay的操作较简单,主要是模拟,

前置知识(代码的大部分)

唯一的前置知识好像就是就是二叉搜索树\(BST\)(\(binary\ search\ tree\))

具有以下特性:

显然是一棵二叉树,

每个节点有一个权值\(val\),

对于每个节点\(k\),要么其左子树为空,否则其左子树的所有元素节点权值都小于\(val[k]\),

对于其右子树,要求其中权值全部大于\(val[k]\),

如果整棵树中有几个节点权值相等,那么将这个元素对应的节点多开一个域\(sum\),表示这个权值的元素的个数

树中所有子树全是\(BST\)

这样一来,形态大致如下:

我们为什么要设立这样一个建立结构的规则呢?

显然这样非常适合数的查找,基本上就是二分的思路,无论是访问第\(k\)小值,还是访问权值为\(v\)的节点,都可以快速地实现目标,插入也是如此

具体步骤(比如找权值为\(v\)的元素):

从根节点开始寻找:

对于当前节点\(k\),如果现在\(v = val[k]\),

否则,如果当前值较小,说明目标值一定在左子树,查找左儿子,否则去查找右儿子,

重复2-3步,直到出现以下两种状况:

在查找过程中如步骤2,找到节点,完成查找,

直到找到最下方的空节点,也没有找到目标节点,说明这个节点并不存在

(查询操作一般不会这样的)

p.s. 对于插入操作,如果找到第二种情况的空节点,那么说明可以直接插入这个新节点,毕竟之前没有这个节点,新建就完了

这里注意,我们处理这些信息根本不用递归,只需要写个函数然后在函数之内跑循环就完了,这样更加高效

这就是\(BST\)处理元素的主要原理,步骤

可以发现这样类似于二分的方法是非常高效的,可以方便地维护出数列中与大小关系有关的数据

比如说:

求第\(k\)大的数的值

求大小为\(v\)的元素的排名

求比\(v\)大的最小数(后继)

求比\(v\)小的最大数(前驱)

求区间内第\(k\)大的数(并不)

注意区间求值是树套树的操作,不是\(BST\)的操作

那么在没接触Splay的情况下,我们来看一看完全不需要Splay的一些操作及函数实现:

所需变量 int ch[N][2],f[N],size[N],sum[N],val[N]; int rt,cnt;

我们一般采用动态开点的操作来进行元素插入,说的通俗就是随着用随着开

这里变量\(cnt\)就是这样一个作用,开点的时候:

++cnt; nd[cnt]=...;

rt存的是当前\(BST\)的根

\(ch[k][0/1]\)储存的是每个节点的左右儿子,\(ch[k][0]\)为左,\(ch[k][1]\)为右,

\(f[k]\)储存的则是节点父亲,\(f[rt]=0\),

\(val[k]\)存的是\(k\)节点的值,

\(size[k]\)存以节点\(k\)为根的子树的大小,

\(sum[k]\)存元素\(k\)出现的次数,就是序列中有几个值为\(val[k]\)的元素

最基础的函数-更新函数update

用于更新节点信息,具体作用其实就是维护子树大小,节点的关系不改变

inline void update(ci x){ if(!x) return ; size[x]=sum[x]; if(ch[x][0]) size[x]+=size[ch[x][0]]; if(ch[x][1]) size[x]+=size[ch[x][1]]; }

翻译:

空节点则直接弃疗 否则先将当前子树大小赋值为当前元素个数 如果有左儿子就加上左儿子的子树大小,右儿子同理

这里注意这个函数的使用,\(update\)操作一定要先对子节点再对父节点,这样才能保证正确性,

因为越是深度小的节点,其信息就越是从子节点合并上来的,如果先更新父节点,父节点的信息很可能没有被"最新"子节点信息更新,反而被未更新的"老"子节点更新,在信息访问的时候会爆炸,实为下策

如果先维护子节点,那么其所有祖宗节点的更新都有了保障,因为其使用的子节点信息全部是更新过的,

插入函数\(insert\)

主要实现思想在上面了,

代码给出,

inline void insert(ci x){ if(!rt){ cnt++; ch[cnt][0]=ch[cnt][1]=f[cnt]=0; rt=cnt; size[cnt]=sum[cnt]=1; val[cnt]=x; return ; }int now=rt,fa=0; while(1){ if(val[now]==x){ sum[now]++; update(now); update(fa); return ; }fa=now; now=ch[now][val[now]


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3